/*
 * Copyright (c) 2010, Christian Lerche
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Author: Christian Lerche <christian.lerche@uni-rostock.de>
 *
 */

/*TODO:
 * - check sendm_xxx() functions for return value!
 *   (handle sendm errors internally to avoid checking every function!!!
 *  e.g. send an std. error if something nasty happened)
 * SOAP error must also be a http error
 *
 * */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* Debug section as you will find it in every uDPWS file */
#define DEBUG                 UDPWS_DBG_LEVEL_HTTP /* 0 - No Debug -> 5 - Full Debug */
#define UDPWS_DEBUG_MODULE    "HTTP"
#include "udpws-debug.h"

#include "http.h"

const char* http_post = "POST";
const int http_post_len = 4;
const char* http_doubleLF = "\r\n\r\n";
const int http_doubleLF_len = 4;

const char http_ok[84] = "HTTP/1.0 200 OK\r\n"
    "Content-Type: application/soap+xml; charset=UTF-8\r\n"
    "Content-Length: ";
int http_ok_len = 84;

const char http_err_400[24] = "HTTP/1.0 400 Bad Request";
int http_err_400_len = 24;

const char http_err_405[31] = "HTTP/1.0 405 Method Not Allowed";
int http_err_405_len = 31;

const char http_err_411[28] = "HTTP/1.0 411 Length Required";
int http_err_411_len = 28;

const char http_err_500[34] = "HTTP/1.0 500 Internal Server Error";
int http_err_500_len = 34;

char cont_len_str_buf[5];

//static int http_get_next_line(char* buf, int* len, int* line_len, char** next_line);
static int http_copy_to_buffer(struct http_con *hc, char* p, int len);
static void http_send_error(struct http_con *hc, struct sendm_s *sm, int err);
void http_found_line_end(struct http_con *hc, char* buf, char* buf_end);

int http_init(struct http_con *hc) {
    /* TODO: this is the place where we could allocate the http buffer
     * to make it more generic */
    http_new(hc);
    return HTTP_OK;
}

void http_new(struct http_con *hc) {
    /*First state is to recv the HTTP header*/
    hc->state = HTTP_STATE_REQUEST_HEADER;
    hc->cont_len = 0;
    hc->buf_len = 0;
    hc->rcvd_req_line = 0;
    /*TODO: start a timer? or were should we do this */
}

int http_in(struct http_con *hc, struct sendm_s *sm, char* buf, int len) {
    DBG_PRINT_VERBOSE("http_in called...");
    char *p = buf;
    char *buf_rest = buf; /* rest of buffer we need to handle */
    char *buf_end = buf + len;
    int buf_rest_len = len;

    /* every time a new tcp packet arrives http_in() is called */
    if (hc->state == HTTP_STATE_PROCESS || hc->state == HTTP_STATE_RESPONSE || hc->state == HTTP_STATE_CLOSED) {
        /* ignore incoming data if we are in PROCESS or RESPONSE mode or
         * we want to close the connection */
        return HTTP_OK;
    }

    if (hc->state == HTTP_STATE_REQUEST_HEADER) {
        /* we just search for the \n char and ignore the \r char */
        /* go through the fragment and search for \n */
        while (p < buf_end) {
            if (*p == '\n') {
                /* found a line end char */
                /* TODO: Error Handling:
                 * 		invalid header?
                 * 		header parsing finished?*/
                http_found_line_end(hc, buf_rest, p);
                /* next */
                buf_rest = p + 1;
                buf_rest_len = buf_end - buf_rest;
                if (hc->state == HTTP_STATE_REQUEST_CONT || hc->error) {
                    /*this was the last line or an error occurred, we break */
                    break;
                }
            }
            p++;
        }
        /* copy the rest to the http buffer */
        if (hc->state == HTTP_STATE_REQUEST_HEADER) {
            /* we did not reached the end of the header, copy the rest to the buffer
             * and return*/
            http_copy_to_buffer(hc, buf_rest, buf_rest_len);
            return HTTP_OK;
        }
    }

    /* we are here because we either parsed the header before or at the last http_in() call */
    if (hc->state == HTTP_STATE_REQUEST_CONT) {
        DBG_PRINT_INFO("copy content");
        /*copy the content to the buffer (could be zero)*/
        if (!http_copy_to_buffer(hc, buf_rest, buf_rest_len)) {
            /* copy was successful*/
            /* check if request is finished */
            if (hc->buf_len >= hc->cont_len) {
                DBG_PRINT_INFO("received complete content");
                hc->state = HTTP_STATE_PROCESS;
                return HTTP_OK;
            }

        }
    }

    /* was there an error during processing? */
    if (hc->error) {
        http_send_error(hc, sm, hc->error);
        hc->state = HTTP_STATE_CLOSED;
        return HTTP_OK;
    }
    //printf("Copy %i bytes to buffer, remain: %i of %i\n", copy_len, hs.remain_len, hs.cont_len);
    return HTTP_OK;
}

int http_closed(struct http_con *hc) {
    return hc->state == HTTP_STATE_CLOSED;
}

/* parse one line of the header */
void http_found_line_end(struct http_con *hc, char* buf, char* buf_end) {
    /* first check if there is already something in the buffer
     * if there is something we first need to copy the rest to the buffer
     * otherwise we can parse the header line directly on the given buffer */
    if (hc->buf_len) {
        /*there is something in the buffer, we need to copy */
        http_copy_to_buffer(hc, buf, buf_end - buf);
        buf = hc->buffer;
    }

    /* is this the first time, than we require a POST*/
    if (!hc->rcvd_req_line) {
        if (strncasecmp("POST", buf, 4) != 0) {
            /* Not a valid HTTP Header */
            DBG_PRINT_ERROR("not a HTTP POST request");
            hc->error = HTTP_ERR_NOT_POST_REQ;
            hc->state = HTTP_STATE_ERROR;
            goto finish;
        }
        /* Now we received the first request line */
        hc->rcvd_req_line = 1;
    }

    /* check for an empty line than we finished */
    if (*buf == '\r') {
        /* found empty line */
        DBG_PRINT_INFO("found empty line in header");
        /*TODO: check for all required header lines*/
        if (hc->cont_len <= 0) {
            DBG_PRINT_ERROR("Content Length required");
            hc->error = HTTP_ERR_NO_CONT_LEN;
            hc->state = HTTP_STATE_ERROR;
            goto finish;
        }
        hc->state = HTTP_STATE_REQUEST_CONT;
        goto finish;
    }

    /* parse http line */
    if (strncasecmp("CONTENT-TYPE:", buf, 13) == 0) {
        /* TODO: check content type */
        goto finish;
    }

    if (strncasecmp("CONTENT-LENGTH:", buf, 15) == 0) {
        DBG_PRINT_INFO("Found Content Length");
        hc->cont_len = atoi(&buf[16]);
        goto finish;
    }

    finish:
    /* free buffer at the end, in our case this means to set size to zero */
    hc->buf_len = 0;
    return;
}

static int http_copy_to_buffer(struct http_con *hc, char* p, int len) {

    /* TODO: optimize: remove white-space characters that are not necessary*/
    if (!len) {
        /* there is nothing to copy */
        return HTTP_OK;
    }
    if (len > (HTTP_BUFFER_SIZE - hc->buf_len)) {
        /* buffer not big enough */
        DBG_PRINT_ERROR("http_copy_to_buffer(): buffer not big enough");
        return HTTP_ERR_MEM;
    }
    memcpy(&hc->buffer[hc->buf_len], p, len);
    hc->buf_len += len;
    //	printf("Add %i bytes to http buffer (new size: %i)\n", len, hs.buf_len);
    return HTTP_OK;
}

void http_finished_processing(struct http_con *hc, struct sendm_s *sm, int err) {
    int len;
    len = sendm_get_len(sm);
    /* result with an error so add HTTP error header
     * TODO: if error or not, we need to add a header -> one function!*/
    if (err) {
        http_send_error(hc, sm, err);
        return;
    }
    if (len <= 0) {
        /* something is wrong with the sending machine (SOAP error???)*/
        http_send_error(hc, sm, HTTP_ERROR);
        return;
    }
    /*this is maybe a bit confusing:
     * we have to add the http header in the reverse order
     * because we have to add them at the beginning */
    sendm_add_top_ram(sm, http_doubleLF, http_doubleLF_len);
    itoa2(len, cont_len_str_buf);
    sendm_add_top_ram(sm, cont_len_str_buf, strlen(cont_len_str_buf));
    sendm_add_top_ram(sm, http_ok, http_ok_len);
    sendm_start(sm);
    hc->state = HTTP_STATE_CLOSED;
}

static void http_send_error(struct http_con *hc, struct sendm_s *sm, int err) {
    /* TODO: use hc->error */
    switch (err) {
    case HTTP_ERR_INVALID_HEADER:
        sendm_add_top_ram(sm, http_err_400, http_err_400_len);
        break;
    case HTTP_ERR_NOT_POST_REQ:
        sendm_add_top_ram(sm, http_err_405, http_err_405_len);
        break;
    case HTTP_ERR_NO_CONT_LEN:
        sendm_add_top_ram(sm, http_err_411, http_err_411_len);
        break;
    case HTTP_ERR_NO_CONT_TYPE:
    default:
        sendm_add_top_ram(sm, http_err_500, http_err_500_len);
    }
    sendm_add_end_ram(sm, http_doubleLF, http_doubleLF_len);
    sendm_start(sm);
    hc->state = HTTP_STATE_CLOSED;
}

